﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Engine.Mathematics
{

    /** space sensible sparse matrix implementation. */
    public class Matrix
    {
        private double[,] _store;
        /** Number of rows. */
        private int _rows;
        /** Number of clos. */
        private int _cols;

        /** N - number of rows.
         * M - number of column. */
        public Matrix(int N, int M) 
        {
            _store = new double[N, M];
            this._rows = N;
            this._cols = M;
        }

        public Matrix(double[,] data) 
        {
            this._rows = data.GetLength(0);
            this._cols = data.GetLength(1);
            _store = data;
        }

        public double this[int i, int j]
        {
            get
            {
                //get value of array element
                return _store[i,j];
            }
            set
            {
                //set value of array element
                _store[i, j] = value;
            }
        }

        /// <summary>
        /// used in operation with alglib library.
        /// </summary>
        public double[,] Data 
        {
            get { return _store; }
        }

        /// <summary>
        /// Number of rows in matrix
        /// </summary>
        public int Rows
        {
            get { return _rows; }
        }

        ///<summary>
        /// Number of cols in matrix
        ///</summary>
        public int Cols
        {
            get { return _cols; }
        }


        public static Vector operator *(Matrix a, Vector b)
        {
            if((a._store.GetLength(0) != a._store.GetLength(1)) || (a._store.GetLength(1) != b.store.GetLength(0)))
            {
                throw new ArgumentException("Number of column in Matrix a is not equals to number of elements in Vector b");
            }
            int lenght = b.store.GetLength(0);
            double[] resultVector = new double[lenght];
            for (int i = 0; i < lenght; i++)
            {
                double ij = 0;
                for (int j = 0; j < lenght; j++)
                {
                     ij += a._store[i,j]*b.store[j];
                }
                resultVector[i] = ij;
            }
            return new Vector(resultVector);
        }

        public static Matrix operator *(Matrix a, Matrix b)
        {
            if (a == null || b == null)
            {
                throw new NullReferenceException("One of input parameters is null");
            }
            else if(a.Cols != b.Rows)
            {
                throw new InvalidOperationException("We can't do operation on this operand.");
            }
            int lenght = b._store.GetLength(0);
            double[,] resultMatrix = new double[a.Rows,b.Cols];
            for (int i = 0; i < a.Rows; i++)
            {
                for (int j = 0; j < b.Cols; j++)
                {
                    double ij = 0;
                    for (int c = 0; c < a.Cols ; c++ )
                    {
                        ij += a._store[i, c] * b._store[c,j];
                    }
                    resultMatrix[i, j] = ij;
                }
            }
            return new Matrix(resultMatrix);
        }


        /** This method modificate the structure 
         * of the called matrix. 
         * Then of cause there will be huge optimization for dense matrix.
         */
        public void Transpose() 
        {
            for(int i = 0 ; i < _rows ; i++)
            {
                for (int j = 0; j < i; j++ )
                {
                    double toSwap = _store[j, i];
                    _store[j,i] = _store[i,j];
                    _store[i, j] = toSwap;
                }
            }
        }

        public void RemoveLastRow()
        {
            double[,] copy = new double[_rows - 1, _cols];
            Array.Copy(_store, copy, _cols * (_rows - 1));
            //then we assign reference to new array
            this._store = copy;
            _rows-- ;
        }

        /**Transpose and Return this matrix. */
        public Matrix T 
        {
            get 
            { 
                Transpose();
                return this;
            }
        }


        public static Matrix GetRandomMatrix(int N, int M)
        {
            Matrix x = new Matrix(N,M);
            Random random = new Random();
            for (int i = 0; i < N; i++ )
            {
                for (int j = 0; j < M; j++ )
                {
                    x[i, j] = random.NextDouble();
                }
            }
            return x;
        }
    }
}
